package plus.lgx.textsearcher.searcher.handler;

import cn.hutool.core.util.StrUtil;
import plus.lgx.textsearcher.config.SearcherConfig;
import plus.lgx.textsearcher.entity.FileWrapper;
import plus.lgx.textsearcher.entity.PrintData;
import plus.lgx.textsearcher.entity.StringWrapper;
import plus.lgx.textsearcher.searcher.printer.OutputHandler;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static plus.lgx.textsearcher.constant.Const.LS;

/**
 * Class name: LineHandler
 *
 * Create time: 12/27/22 3:54 PM
 *
 * @author lgx
 * @version 1.0
 */
public class LineHandler extends AbstractFileHandler {

    public LineHandler(SearcherConfig config, OutputHandler handler, PrintData data) {
        super(config, handler, data);
    }

    private String content;
    private boolean first;
    private final String sc = config.searchContent;
    private final int len = sc.length();
    private final int mec = config.maxExtendCharsCount;

    @Override
    public void handle(FileWrapper wrapper) {
        if ((content = getContent(wrapper)) == null)
            return;

        first = true;
        switch (config.extenderEnum) {
            case LINE_ -> handleLine(wrapper);
            case MULTILINE_ -> handleMultiline(wrapper);
            default -> throw new IllegalStateException("Unexpected extend method in line handler. ");
        }

        if (!first) {
            handler.printFileTail();
        }
    }

    private void handleLine(FileWrapper wrapper) {
        if (config.skip == null) {
            content.lines().forEach(l -> {
                int ind;
                if ((ind = config.ignoreCase ? StrUtil.indexOfIgnoreCase(l, sc) : l.indexOf(sc)) >= 0) {
                    if (first) {
                        first = false;
                        data.flushFileHead(wrapper);
                        handler.printFileHead();
                    }
                    data.itemNumber = wrapper.addAndGetMatchedNumber();
                    data.totalItemNumber++;
                    data.item = l.substring(Math.max(ind - mec, 0), Math.min(ind + mec + len, l.length()));
                    handler.printItem();
                }
            });
            return;
        }
        handleLineSkip(wrapper);
    }

    private void handleLineSkip(FileWrapper wrapper) {
        assert config.searchContentRegex != null;
        content.lines().forEach(l -> {
            Matcher matcher = config.searchContentRegex.matcher(l);
            if (matcher.find()) {
                if (first) {
                    first = false;
                    data.flushFileHead(wrapper);
                    handler.printFileHead();
                }
                data.itemNumber = wrapper.addAndGetMatchedNumber();
                data.totalItemNumber++;
                data.item = l.substring(Math.max(matcher.start() - mec, 0), Math.min(matcher.end() + mec, l.length()));
                handler.printItem();
            }
        });
    }

    private StringWrapper[] lines;
    private int lineIndex;

    private void handleMultiline(FileWrapper wrapper) {
        if (config.skip != null) {
            handleMultilineSkip(wrapper);
            return;
        }

        lines = content.lines()
                .map(s -> new StringWrapper(s, config.ignoreCase
                        ? StrUtil.indexOfIgnoreCase(s, sc) : s.indexOf(sc)))
                .toArray(StringWrapper[]::new);
        for (lineIndex = 0; lineIndex < lines.length; lineIndex++) if (lines[lineIndex].index >= 0) {
            StringBuilder sb = merge();
            if (first) {
                first = false;
                data.flushFileHead(wrapper);
                handler.printFileHead();
            }
            data.itemNumber = wrapper.addAndGetMatchedNumber();
            data.totalItemNumber++;
            data.item = sb.substring(
                    Math.max(0, sb.indexOf(sc) - mec),
                    Math.min(sb.length(), sb.lastIndexOf(sc) + len + mec)
            );
            handler.printItem();
        }
    }

    private void handleMultilineSkip(FileWrapper wrapper) {
        assert config.searchContentRegex != null;
        lines = content.lines().map(s -> {
            Matcher matcher = config.searchContentRegex.matcher(s);
            return matcher.find()
                    ? new StringWrapper(s, matcher.start(), matcher.end() - matcher.start())
                    : new StringWrapper(s, -1);
        }).toArray(StringWrapper[]::new);

        String pattern = config.searchContentRegex.pattern(),
                s1 = "%s".formatted(pattern),
                s2 = "%s.*?%s".formatted(pattern, pattern);

        int smallestLinesCount = config.lines * 2 + 1;

        for (lineIndex = 0; lineIndex < lines.length; lineIndex++) {
            if (lines[lineIndex].index >= 0) {
                int startLine = lineIndex;
                StringBuilder sb = merge();
                int endLine = lineIndex;
                if (first) {
                    first = false;
                    data.flushFileHead(wrapper);
                    handler.printFileHead();
                }
                data.itemNumber = wrapper.addAndGetMatchedNumber();
                data.totalItemNumber++;
                Pattern p = Pattern.compile(endLine - startLine <= smallestLinesCount ? s1 : s2);
                Matcher matcher = p.matcher(sb);
                if (!matcher.find()) {
                    System.err.format("regular expression match exception. regex: '%s', string: '%s'%n",
                            p.pattern(), sb);
                    data.item = sb.toString();
                } else {
                    int start = Math.max(0, matcher.start() - mec), end = matcher.end() + mec;
                    while (matcher.find()) end = matcher.end() + mec;
                    data.item = sb.substring(start, Math.min(sb.length(), end));
                }
                handler.printItem();
            }
        }
    }

    private StringBuilder merge() {
        int curLine, endLine, charsCount, startLine = Math.max(0, lineIndex - config.lines);
        for (curLine = lineIndex + 1, endLine = Math.min(lineIndex + config.lines, lines.length - 1),
                     charsCount = lines[lineIndex].length - lines[lineIndex].index - len; curLine <= endLine; curLine++) {
            if (lines[curLine].index >= 0 && lines[curLine].index < mec) {
                endLine = curLine + config.lines;
                charsCount = lines[curLine].length - lines[curLine].index - len;
            } else {
                charsCount += lines[curLine].length;
                if (charsCount > mec) {
                    endLine = Math.min(endLine, curLine + 1);
                    break;
                }
            }
        }
        lineIndex = curLine - 1;
        StringBuilder sb = new StringBuilder();
        for (int k = startLine; ; k++) {
            sb.append(lines[k].s);
            if (k >= endLine) return sb;
            sb.append(LS);
        }
    }
}
